package com.hspedu.spring.annotation;

import com.hspedu.spring.hspapplicationcontext.HspApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;

/**
 * HspSpringApplicationContext 类似于 spring原生的ioc容器
 */
public class HspSpringApplicationContext {

    private Class configClass;

    // ioc存放的就是通过反射创建的对象(基于注解的方式)
    private ConcurrentHashMap<String, Object> ioc = new ConcurrentHashMap<>();

    public HspSpringApplicationContext(Class configClass) {
        this.configClass = configClass;
        System.out.println("this.configClass =" + this.configClass);
        // 获取要扫描的包是什么
        // 1. 先得到HspSpringConfig配置的@ComponentScan注解
        ComponentScan componentScan = (ComponentScan)this.configClass.getDeclaredAnnotation(ComponentScan.class);
        // 2. 通过componentScan的value 即要扫描的包
        String path = componentScan.value();
        System.out.println("path=" + path);
        // 3. 获取包下所有的.class文件
        // 3.1、先得到类的加载器
        ClassLoader classLoader = HspApplicationContext.class.getClassLoader();
        // 3.2、通过类的加载器获取到要扫描包的资源url
        path = path.replace(".", "/");
        URL resource = classLoader.getResource(path);
        System.out.println("resource=" + resource);
        // 4. 将要加载的资源 路经下的文件进行遍历
        File file = new File(resource.getFile());
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) {
                // System.out.println("=============");
                // D:\04-Work\Migu\springhsp\out\production\spring\com\hspedu\spring\component\UserService.class
                // com.hspedu.spring.component.UserService
                String absoluteFile = f.getAbsolutePath();
                // 这里只处理.class文件
                if (!absoluteFile.endsWith(".class")) {
                    continue;
                }
                // System.out.println(f.getAbsoluteFile());
                // 1. 获取到类名
                String className = absoluteFile.substring(absoluteFile.lastIndexOf("\\") + 1, absoluteFile.indexOf(".class"));
                // System.out.println("className=" + className);
                // 2. 获取类的完整路径
                // com.hspedu.spring.component.userService
                String classFullName = path.replaceAll("/", ".") + "." + className;
                // System.out.println("classFullName=" + classFullName);
                // 3. 拿到全类名了 进行过滤
                // 判断该类是否需要注入到容器中 只有标注了@Service @Controller @Repository @Component注解的类
                try {
                    // 这时我们就得到了该类的class对象
                    // Class.forName 和 classLoader.loadClass 这两种获取class的方式有啥区别？
                    // 区别：Class.forName会调用该类的静态方法 classLoader.loadClass则不会 也就是说Class.forName会更完整一些
                    Class<?> aClass = classLoader.loadClass(classFullName);
                    if (aClass.isAnnotationPresent(Component.class) || aClass.isAnnotationPresent(Controller.class) || aClass.isAnnotationPresent(Service.class) || aClass.isAnnotationPresent(Repository.class)) {
                        // 演示Component注解指定value 分配id
                        if (aClass.isAnnotationPresent(Component.class)) {
                            // 获取到该注解
                            Component component = aClass.getDeclaredAnnotation(Component.class);
                            // 自定义的value beanId
                            String value = component.value();
                            if (!"".endsWith(value)) {
                                className = value;
                            }
                        }
                        // 这时就可以反射对象 并放入容器中
                        Class<?> clazz = Class.forName(classFullName);
                        Object instance = clazz.newInstance();
                        // 放入容器
                        ioc.put(StringUtils.uncapitalize(className), instance);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public Object getBean(String key) {
        return ioc.get(key);
    }

}
